<html>
    <head>
        <title>深入理解响应式原理</title>
    </head>
    <body>
          <script>
            // 深入理解响应式原理
                    /*
                        现在是时候深入一下了！  Vue 最独特的特性之一，是"非侵入性的响应式系统"。
                        数据模型仅仅是普通的JavaScript对象。而当你修改它们时，视图会进行更改。这使得状体管理变得非常简单直接。
                        不过理解其工作原理同样重要，这样你可以避开一些常见的问题。
                    */

                    //一、如何追踪变化
                            /*
                                Object.defineProperty 
                                        Object.defineProperty 是JavaScript内置的方法，是 ES5中不可shim的。
                                        它由浏览器或其他JavaScript运行时环境(如Node.js)提供。

                                    作用： 用于直接操作对象的属性，允许开发者定义或修改对象的自有属性，并且能可以设置改属性的特性。

                                           是Vue2.x中实现响应式数据绑定的核心方法之一。

                                           它用于直接操作对象的属性，可以定义或修改事情的自有属性，并且可以设置改属性的特性（如是否可枚举、是否可配置等等）
                                           在Vue的响应式系统中，Object.defineProperty 主要用于拦截对于对象属性的"读取" 和 "设置" 操作。
                            */
                            /*
                                当你把一个普通的JavaScript对象传入Vue实例作为 data 选项，
                                Vue 将遍历此对象所有的 property,并使用 Object.defineProperty 把这些 propety 全部转为 getter / setter.
                                这些 getter/setter 对于用户来说是不可见的，但是在内部他们让Vue能够追踪依赖，在 property被访问和修改时同时变更。

                                这里需要注意的是不同浏览器在控制台打印数据对象时，对 getter/setter 的格式化并不同，
                                所以建议安装 vue-devtools来获取对检查数据更加友好的用户界面。

                                每个组件实例都对应一个 watcher 实例，他会在组件渲染过程中把"接触"过的数据property记录为依赖。
                                之后当依赖的setter被触发时，会通知watcher ，从而使它关联的组件重新渲染。
                                （每个vue实例内部都有一个watcher,会把组件渲染时接触过的数据property记录为依赖。）
                            */

                    // 二、检测变化的注意事项
                            /*
                                由于JavaScript的限制，Vue "不能检测数组和对象的变化".
                                尽管如此，我们还是有一些办法来回避这些限制，并保证它们的响应性。
                            */

                            // 1、对于对象
                                    /*
                                        Vue 无法检测property的添加或移除。

                                        由于Vue会在初始化实例时对property执行 getter / setter 转化，

                                        所以，property 必须在 data 对象上存在才能让Vue将它转换为响应式的。
                                    */
                                    // 例如：
                                            var vm = new Vue({
                                                data:{
                                                    a:1
                                                }
                                            })

                                            // 'vm.a' 是响应式的
                                             
                                            vm.b=2;
                                            // 'vm.b' 是非响应式的

                                    /*
                                        对于已经创建的实例，Vue"不允许"动态添加 "根级别" 的响应式 property. 

                                        但是， 可以使用 Vue.set(vm.someObject,'b',2)方法向"嵌套对象"添加响应式 property.
                                    */
                                     // 例如：
                                            Vue.set(vm.someObject,'b',2);
                                    
                                    // 您还可以使用 vm.$set 实例方法，这也是全局 Vue.set 方法的别名：
                                            this.$set(this.someObject,'b',2);

                                    /*
                                        有时候你可能需要为已有对象赋值多个新 property,比如使用 Object.assign() 或 _.extend().

                                        但是，这样添加到对象上的新 property 是非响应式的，不会触发更新。

                                        在这种情况下，你应该用原对象与要混合进去的对象的 property 一起创建一个新对象。
                                    */
                                            // Object.assign() 用来做对象的合并，是浅层拷贝，不会做引用的拷贝，而是直接给引用赋值。
                                            // 直接操作this.someObject :  Object.assign(this.someObject,{a:1,b:2})是无效的，不会触发更新
                                            this.someObeject = Object.assign({},this.someObject,{a:1,b:2})


                                // 2、对于数组
                                    /*
                                        Vue不能检测一下数组的变动：
                                             ①、当你利用索引直接设置一个数组项时， 例如： vm.items[indexOfItem] = newValue
                                             ②、当你修改数组的长度时，例如：  vm.items.length = newLength
                                    */
                                    // 举个例子：
                                            var vm = new Vue({
                                                data:{
                                                    items:[
                                                        'a',
                                                        'b',
                                                        'c' 
                                                    ]
                                                }
                                            })

                                    /*
                                        为了解决第一类问题，一下两种方式都可以实现和 vm.items[indexOfItem]= newValue 相同的效果，
                                        同时也将在响应式系统内触发状态更新：
                                    */
                                            //①、 Vue.set
                                            Vue.set(vm.items,indexOfItem,newValue);

                                            

                                           //②、Array.prototype.splice
                                                        /*
                                                                slice() 是JavaScript数组的一个非常强大的内置方法，用于添加或删除数组中的元素。
                                                                它可以直接修改原数组，并并返回被删除的数组元素（如果有）
                                                            */
                                                            array.splice(startIndex,deleteCount,item1,item2,...)
                                                            /*
                                                                start(必须)： 修改的其实位置，如果为负数则从数组末尾考试啊计算。

                                                                deleteCount(可选)： 要删除的元素个数。如果设置为0或者省略，则不删除任何元素

                                                                item1,item2,...(可选)： 要添加到数组中的新元素。可以添加多个元素。
                                                            */
                                            vm.items.splice(indexOfItem, 1, newValue)

                                    // 你也可以使用 vm.$set 实例方法，该方法是全局方法 Vue.set 的一个别名：
                                                    vm.$set(vm.items,indexOfItem,mewValue);

                // 三、声明响应式属性 property
                            /*
                                由于Vue不允许动态添加根级别的响应式 property,所以你必须在初始化实例签声明所有根级响应式 property,哪怕只是一个空值：
                            */
                                    var vm= new Vue({
                                        data:{
                                            // 声明一个 message 为一个空字符串
                                            message:''
                                        },
                                        template:'<div>{{message}}</div>'
                                    })

                                    // 之后设置 'message'
                                    vm.message = 'Hello';
                             /*
                                如果你未在 data 选项中声明 message, Vue将警告你渲染函数正在视图访问不存在的 property.

                                这样的限制在背后是有其技术原因的，它消除了在依赖项跟踪系统中的一类边界情况，也使Vue实例能够更好地配合类型检查系统工作。

                                但与此同时，在代码可维护性方面也有一点重要的考虑： data 对象就像组件状态的结构。

                                提前声明所有的响应式 property ,可以让组件代码在未来修改或者其他开发人员阅读时更容易理解。
                             */

                // 四、异步更新队列
                            /*
                                可能你还没有注意到，Vue在更新DOM时是"异步执行的"。
                                只要侦听到数据变化，Vue将开启一个队列，并缓冲在同一时间循环中的所有数据变更。
                                如果同一个 watcher 被多次触发，只会推入到队列中一次。这种在缓冲时去除重复数据对于避免不必要的计算和DOM操作时非常重要的。
                                然后，在下一个的事件"tick"中，Vue刷新队列并执行实际工作。
                                Vue在内部队列尝试使用原生的 Promise.then 、MutationObserver  和 setImmediate ，
                                如果执行环境不支持，则会采用 setTimeout(fn,0)代替。

                                例如，当你设置 vm.someData = 'new value' ,该组件不会立即重新渲染。
                                当刷新队列时，组件会在下一个事件循环'tick'中更新。多数情况我们不需要关心这个过程，但是如果你想基于更新后的DOM状态来做点什么，
                                这就可能会有些棘手。
                                虽然Vue通常鼓励开发者使用"数据驱动"的方式思考，避免直接操作ＤＯＭ，但是有时我们必须这么做。
                            */

                            //  为了在数据变化之后等待Vue完成更新DOM，可以在数据变化之后使用该 Vue.nextTick(callback).这样回调函数将在DOM更新完成后被调用。
                            //  例如：
                                       var vm = new Vue({
                                         el:'#example',
                                         data:{
                                            message:'123'
                                         }
                                       })

                                       vm.message = 'new message';// 更改响应式数据
                                       vm.$el.textContent==='new message'; // 此时表达式值为： false
                                       Vue.nextTick(function(){  // 下一个DOM更新队列执行完毕时执行回调
                                            vm.$el.textContent ==='new message'; // 此时表达式值为： true
                                       })

                            /*
                                在组件内使用 vm.$nextTIck() 实例方法特别方便，因为它不需要全局Vue,并且回调函数中的 this 将自动绑定到当前的Vue实例上。
                            */
                                       Vue.component('example',{
                                          template:'<span>{{message}}</span>',
                                          data:function(){
                                            return {
                                                message:'未更新'
                                            }
                                          },
                                          methods:{
                                            updateMessage:function(){
                                                this.message= '已更新'
                                                console.log(this.$el.textContent) // =>'未更新'
                                                this.$nextTick(function(){
                                                    console.log(this.$el.textContent) // => '已更新'
                                                });
                                            }
                                          }
                                       })


          </script>
    </body>
</html>